/*
 * $Id$
 * Sleeper
 *
 * Created by Jon Gordon on 7/24/08
 * Copyright Jon Gordon.  All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */


#include <stdlib.h>

#import "Sleeper.h"

#import "QEExchangeResolver.h"
#import "QEExchangeElement.h"
#import "QEExToken.h"
#import "QETokenizer.h"


@interface QEExchangeResolver (private)
- (NSMutableSet *)legalElementsForString:(NSString *)source;

// returns YES if the assignment locked out any possibilities, NO otherwise
- (BOOL)assignElement:(QEExchangeElement *)element
              toToken:(QEExToken *)token
          inMatchList:(NSMutableDictionary *)matchList;

- (void)setExchangeValid:(BOOL)newExchangeValid;

- (void)setElements:(NSDictionary *)newElements;
- (NSDictionary *)elements;

- (void)setSourceTokens:(NSDictionary *)newSourceTokens;
- (NSDictionary *)sourceTokens;

- (void)setResolvedElements:(NSDictionary *)newResolvedElements;
@end


//
// Attempts to resolve a list of strings (tokens) into elements of a
// November Sweepstakes exchange.
//
// Author: $Author: jon@bway.net $
// Version: $Revision: 24 $
//
@implementation QEExchangeResolver

static NSArray *elementNames;


#pragma mark LIFECYCLE METHODS
+ (void)initialize
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    static BOOL initialized = NO;
    if (!initialized) {
        initialized = YES;
        NSLog(@"initializing QEExchangeResolver");
        elementNames = [[NSArray alloc] initWithObjects:@"freq",
                        @"serialRcvd",
                        @"prec",
                        @"call",
                        @"check",
                        @"section",
                        nil];
    } // if
} // initialize


- (QEExchangeResolver *)init
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    self = [super init];
    
    if (nil != self) {
        exchangeValid = NO;
        
        NSMutableDictionary *tmpElements = [NSMutableDictionary dictionaryWithCapacity:[elementNames count]];
        NSEnumerator *e = [elementNames objectEnumerator];
        NSString *elementName;
        QEExchangeElement *element;
        while (elementName = [e nextObject]) {
            element = [QEExchangeElement elementForName:elementName];
            [tmpElements setObject:element forKey:elementName];
        } // while
        
        elements = [[NSDictionary alloc] initWithDictionary:tmpElements];
        resolvedElements = [[NSMutableDictionary alloc] init];
        sourceTokens = [[NSMutableDictionary alloc] init];
        tokenizer = [[QETokenizer alloc] init];
    } // if
    return self;
} // init


- (void)dealloc
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    [tokenizer release];
    [elements release];
    [resolvedElements release];
    [sourceTokens release];
    [super dealloc];
} // dealloc


#pragma mark PROPERTIES
- (NSDictionary *)resolvedElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[resolvedElements retain] autorelease];
} // resolvedElements


- (BOOL)isExchangeValid
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return exchangeValid;
} // isExchangeValid


+ (NSArray *)elementNames
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[elementNames copy] autorelease];
} // elementNames


#pragma mark OPERATIONS
- (BOOL)resolveExchange:(NSString *)inputExchange
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    
    if (inputExchange) {
        NSLog(@"  exchange: \"%@\"", inputExchange);
    } else {
        NSLog(@"  input exchange is nil--resetting to empty string");
        inputExchange = @"";
    } // if
    BOOL newExchangeValid = NO;
    
    NSMutableDictionary *newElements = nil;
    NSMutableDictionary *newSourceTokens = nil;
    NSArray *tokens = nil;
    
    if ([inputExchange length] > 0) {
        tokens = [tokenizer tokenize:inputExchange]; 
    } // if
    
    /* We can't use the strings that include the tokens as keys to the various
       dictionaries when we resolve the tokens below, because keys have to
       be unique, while exchange elements (and therefore tokens) have no such
       limitation.  So we wrap each string in an instance of QEExToken, which
       includes the position of each token in the input string, and is therefore
       unique. */
    if ((nil != tokens) && ([tokens count] > 0)) {
        unsigned int i;
        unsigned int count = [tokens count];
        NSMutableArray *newTokens = [[NSMutableArray alloc] initWithCapacity:count];
        QEExToken *wrappedToken;
        for (i = 0; i < count; ++i) {
            wrappedToken = [[QEExToken alloc] initWithValue:[tokens objectAtIndex:i]
                                                   atPosition:i];
            [newTokens insertObject:wrappedToken atIndex:i];
            [wrappedToken release];
        } // for
        tokens = [newTokens autorelease];
    } // if
    
    if ((nil != tokens) && ([tokens count] > 0)) {
        newElements = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        newSourceTokens = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        NSMutableDictionary *legalValuesForTokens = [NSMutableDictionary dictionaryWithCapacity:[tokens count]];
        
        QEExToken *wToken;
//        NSString *token;
        NSEnumerator *tkEnum = [tokens objectEnumerator];
        while (nil != (wToken = [tkEnum nextObject])) {
            [legalValuesForTokens setObject:[self legalElementsForString:[wToken value]] forKey:wToken];
        } // while
        
        // eliminate possibilities by locking in tokens and elements that have only
        // one possible match
        NSEnumerator *elNamesEnum;
        NSString *elName;
        QEExToken *tkMatch;
        QEExchangeElement *element;
        NSSet *legalValues;
        int count;
        BOOL changed;
        do {
            changed = NO;
            tkEnum = [tokens objectEnumerator];
            while (nil != (wToken = [tkEnum nextObject])) {
                legalValues = [legalValuesForTokens objectForKey:wToken];
                if (1 == [legalValues count]) {
                    changed = [self assignElement:[legalValues anyObject] // OK--we know there's only 1 object 
                                          toToken:wToken 
                                      inMatchList:legalValuesForTokens];
                    if (changed) {
                        break;
                    } // if
                } // if
            } // while
            if (changed) {
                continue;
            } // if
            
            elNamesEnum = [elementNames objectEnumerator];
            while (nil != (elName = [elNamesEnum nextObject])) {
                element = [elements objectForKey:elName];
                count = 0;
                tkEnum = [tokens objectEnumerator];
                while (nil != (wToken = [tkEnum nextObject])) {
                    legalValues = [legalValuesForTokens objectForKey:wToken];
                    NSAssert1(nil != legalValues,
                              @"nil improperly returned as legal values for \"%@\"",
                              [wToken value]);
                    if ([legalValues containsObject:element]) {
                        ++count;
                        tkMatch = wToken;
                    } // if
                } // while
                if (1 == count) {
                    changed = [self assignElement:element // OK--we know there's only 1 object 
                                          toToken:tkMatch 
                                      inMatchList:legalValuesForTokens];
                    if (changed) {
                        break;
                    } // if
                } // if
            } // while
            if (changed) {
                continue;
            } // if
            
            // finally, we apply a heuristic: first come, first served.  That is, when
            // multiple tokens match an element, we take the first token.  By putting
            // the elements in the order that we expect to see them, we should get
            // close enough in most cases.
            elNamesEnum = [elementNames objectEnumerator];
            while (nil != (elName = [elNamesEnum nextObject])) {
                element = [elements objectForKey:elName];
                tkEnum = [tokens objectEnumerator];
                while (nil != (wToken = [tkEnum nextObject])) {
                    if ([[legalValuesForTokens objectForKey:wToken] containsObject:element]) {
                        changed = [self assignElement:element
                                              toToken:wToken 
                                          inMatchList:legalValuesForTokens];
                        if (changed) {
                            break;
                        } // if
                    } // if
                } // while
                if (changed) {
                    break;
                } // if
            } // while
        } while (changed);
        
        // if the entry was valid, we have 1 matching token for each element, and
        // no extra tokens
        newExchangeValid = ([tokens count] == [elementNames count]);
        tkEnum = [tokens objectEnumerator];
        while (nil != (wToken = [tkEnum nextObject])) {
            if (1 == [[legalValuesForTokens objectForKey:wToken] count]) {
                element = [[legalValuesForTokens objectForKey:wToken] anyObject];
                elName = [element elementName];
                id newValue = [element valueForString:[wToken value]];
                [newElements setObject:newValue forKey:elName];
                [newSourceTokens setObject:[wToken value] forKey:newValue];
            } else {
                newExchangeValid = NO;
            } // else
        } // while
    } // if
    
#ifdef QE_VERBOSE_DEBUG
    {
        NSLog(@"  resolved elements:");
        NSEnumerator *debugElements = [newElements keyEnumerator];
        NSString *debugElementName;
        while (nil != (debugElementName = [debugElements nextObject])) {
            NSLog(@"    %@ : %@", 
                  debugElementName,
                  [newElements objectForKey:debugElementName]);
        } // while
    }
#endif
    
    [self setResolvedElements:newElements];
    [self setExchangeValid:newExchangeValid];
    [self setSourceTokens:newSourceTokens];
    return [self isExchangeValid];
} // resolveExchange:


- (NSString *)sourceTokenForResolvedElement:(id)resolvedElement
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[self sourceTokens] objectForKey:resolvedElement];
} // sourceTokenForResolvedElement:
@end


@implementation QEExchangeResolver (private)

- (NSMutableSet *)legalElementsForString:(NSString *)source
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    NSMutableSet *legalElements = [[NSMutableSet alloc] init];
    
    NSEnumerator *elEnum = [elementNames objectEnumerator];
    NSString *elName;
    QEExchangeElement *element;
    while (nil != (elName = [elEnum nextObject])) {
        element = [elements objectForKey:elName];
        if ([element isLegalValueInString:source]) {
            [legalElements addObject:element];
        } // if
    } // while
    
#ifdef QE_VERBOSE_DEBUG
    NSMutableString *elementNameList = [[[NSMutableString alloc] init] autorelease];
    elEnum = [legalElements objectEnumerator];
    while (nil != (element = [elEnum nextObject])) {
        [elementNameList appendFormat:@"%@ ", [element elementName]];
    } // while
    NSLog(@"  source \"%@\": %@", source, elementNameList);
#endif
    
    return legalElements;
} // legalElementsforString:

- (BOOL)assignElement:(QEExchangeElement *)element
              toToken:(QEExToken *)wToken
          inMatchList:(NSMutableDictionary *)matchList
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    BOOL changed = NO;
    
    // start with sanity checks
    if (!element) {
        NSLog(@"  WARNING: nil element, returning unchanged");
        return changed;
    } // if
    
    if (!wToken) {
        NSLog(@"  WARNING: nil token, returning unchanged");
        return changed;
    } // if
    
    if (!matchList) {
        NSLog(@"  WARNING: nil matchList, returning unchanged");
        return changed;
    } // if
    
    if (![matchList objectForKey:wToken]) {
        NSLog(@"  WARNING: nil matchList entry for token \"%@\", returning unchanged", [wToken description]);
        return changed;
    } // if
    
    if (![[matchList objectForKey:wToken] containsObject:element]) {
        NSLog(@"  WARNING: element %@ has already been ruled out for token \"%@\", returning unchanged",
              [element elementName],
              [wToken description]);
        return changed;
    } // if
    
    NSEnumerator *e;
    NSMutableSet *legalElements;
    QEExToken *excludeToken;
    
    legalElements = [matchList objectForKey:wToken];
    if (1 != [legalElements count]) {
        [legalElements removeAllObjects];
        [legalElements addObject:element];
        changed = YES;
    } // if
    
    e = [matchList keyEnumerator];
    while (nil != (excludeToken = [e nextObject])) {
        if (![excludeToken isEqual:wToken] && 
            [[matchList objectForKey:excludeToken] containsObject:element]) {
            [[matchList objectForKey:excludeToken] removeObject:element];
            changed = YES;
        } // if
    } // while
    
    return changed;
} // assignElement:toToken:inMatchList:


- (void)setElements:(NSDictionary *)newElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (elements != newElements) {
        [newElements retain];
        [elements autorelease];
        elements = newElements;
    } // if
} // setElements:

- (NSDictionary *)elements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[elements copy] autorelease];
} // elements


- (void)setExchangeValid:(BOOL)newExchangeValid
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    exchangeValid = newExchangeValid;
} // setExchangeValid:


- (void)setSourceTokens:(NSDictionary *)newSourceTokens
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (sourceTokens != newSourceTokens) {
        [sourceTokens autorelease];
        sourceTokens = [[NSDictionary alloc] initWithDictionary:newSourceTokens];
    } // if
} // setSourceTokens:

- (NSDictionary *)sourceTokens
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    return [[sourceTokens retain] autorelease];
} // sourceTokens


- (void)setResolvedElements:(NSDictionary *)newResolvedElements
{
    NSLog(@"MESSAGE: %s", __PRETTY_FUNCTION__);
    if (resolvedElements != newResolvedElements) {
        [resolvedElements autorelease];
        resolvedElements = [[NSDictionary alloc] initWithDictionary:newResolvedElements];
    } // if
} // setResolvedElements:
@end
